package cn.xtcode.config;

import cn.xtcode.types.annotations.DCCValue;
import cn.xtcode.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author xt-code
 * @Description: 获取Spring 中的Bean属性，查看那些类中的属性配置了@DCCValue铸就
 * @Create 2025/5/8 22:03
 */
@Slf4j
@Configuration
public class DCCValueBeanFactory implements BeanPostProcessor {
    private static final String BASE_CONFIG_PATH = "group_buy_market_dcc_";

    private final RedissonClient redissonClient;

    //记录对象
    private final Map<String, Object> dccObject = new HashMap<>();

    public DCCValueBeanFactory(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 定义一个Spring Bean，用于监听Redis的特定主题消息。
     * 该Bean的名称为"dccTopic"，由Spring容器管理。
     */
    @Bean("dccTopic")
    public RTopic dccRedisTopicListener(RedissonClient redissonClient) {
        // 1. 获取Redisson的RTopic实例，绑定到指定的主题"group_buy_market_dcc"
        // RTopic是Redisson提供的分布式主题接口，用于实现Redis的发布/订阅功能
        RTopic topic = redissonClient.getTopic("group_buy_market_dcc");

        // 2. 为该主题添加消息监听器，监听String类型的消息
        topic.addListener(String.class, new MessageListener<String>() {
            /**
             * 当接收到消息时触发此方法
             * @param charSequence 消息所在的通道名称（即主题名称，此处为"group_buy_market_dcc"）
             * @param message 消息内容（实际接收到的数据）
             */
            @Override
            public void onMessage(CharSequence charSequence, String message) {
                log.info("接收到动态配置信息:{}", message);

                //需要修改的属性名,value
                //1.将消息分割  field属性名,value
                String[] split = message.split(Constants.SPLIT);
                String attribute = split[0];

                //2.Map中的key,由固定路径 + 属性名
                String key = BASE_CONFIG_PATH + attribute;

                String value = split[1];

                //3.向第三方询问,动态配置的属性是否存在
                RBucket<Object> bucket = redissonClient.getBucket(key);
                boolean exists = bucket.isExists();

                //4.不存在，表示属性名有错误
                if (!exists) return;

                bucket.set(value);

                //5.获得Map中的bean，修改这个bean
                Object objBean = dccObject.get(key);

                //对象值不存在
                if (null == objBean) return;

                //6.获取bean的类型
                Class<?> objBeanClass = objBean.getClass();


                //保证不是代理类
                if (AopUtils.isAopProxy(objBean)) {
                    objBeanClass = AopUtils.getTargetClass(objBean);
                }

                try {
                    Field field = objBeanClass.getDeclaredField(attribute);
                    field.setAccessible(true);
                    field.set(objBean, value);
                    field.setAccessible(false);
                    log.info("config success attribute- {}, value-{}", attribute, value);

                } catch (Exception e) {
                    throw new RuntimeException("-----------");
                }
            }
        });
        // 4. 返回RTopic实例，便于其他组件注入后调用其publish方法发送消息
        return topic;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 注意；增加 AOP 代理后，获得类的方式要通过 AopProxyUtils.getTargetClass(bean); 不能直接 bean.class 因为代理后类的结构发生变化，这样不能获得到自己的自定义注解了。
        //1.每个Bean的生命周期中，都执行
        Class<?> targetBeanClass = bean.getClass();
        Object targetBeanObject = bean;


        //2.bean可能是代理类,进行判断。是代理类，获取它的目标类
        if (AopUtils.isAopProxy(bean)) {
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetBeanObject = AopProxyUtils.getSingletonTarget(bean);
        }

        //3.循环遍历类的所有field
        for (Field field : targetBeanClass.getDeclaredFields()) {
            //4.属性上没有@DCCValue注解，那就下一个属性
            if (!field.isAnnotationPresent(DCCValue.class)) {
                continue;
            }

            //5.如果有DCCValue注解
            DCCValue dccValue = field.getAnnotation(DCCValue.class);

            //获得DCCValue的value属性-@DCCValue(value = "downGradeSwatch:0")
            String value = dccValue.value();

            //6.用户没有配这个值  @DCCValue() 仅标注了注解，但是没有写值。抛出异常
            if (StringUtils.isBlank(value)) {
                throw new RuntimeException("exception");
            }

            //7.在注解中配置了值 @DCCValue("downGradeSwatch:0")、@DCCValue("cutRange:100")。会配置这样2个值
            String[] split = value.split(":");
            //-----group_buy_market_dcc_downGradeSwatch，或者其它属性。 注入当前类的Map为了方便配置
            //-----注入Redis为了证明我们初始化了该配置，表明自己来过~~~
            String key = BASE_CONFIG_PATH.concat(split[0]);

            //-----获取注解中的原始值
            String defaultValue = split.length == 2 ? split[1] : null;

            //redis中最初情况下，是不存在值的。只有设置后，才获取值
            //redis中没有值的情况下，就使用自己的初始值
            String setValue = defaultValue;

            try {
                //@DCCValue("downGradeSwatch:") 没有配值
                if (StringUtils.isBlank(defaultValue)) {
                    throw new RuntimeException("exception");
                }

                //设置对象到Redis中是为什么 ----向第三方表明我们有这个属性
                RBucket<String> bucket = redissonClient.getBucket(key);

                boolean exist = bucket.isExists();

                //---初始情况下，Redis中没有这个值
                if (!exist) {
                    bucket.set(defaultValue);
                } else {
                    setValue = bucket.get();
                }

                //属性可访问
                field.setAccessible(true);
                field.set(targetBeanObject, setValue);
                field.setAccessible(false);
            } catch (Exception e) {
                throw new RuntimeException("exception");
            }

            //---------记录操作过的对象，因为等会接收到消息后，需要对他进行操作
            dccObject.put(key, targetBeanObject);
        }

        return bean;
    }
}
